package server.auction;

/**
 * This Thread ServerCommandHandlerThread receives messages and decides
 * what to do with (login user, list auctions, bid, ...).
 *
 * @version 1.0 vom 16.10.2012
 * @author Peter Tillian 1026312
 */

import java.io.IOException;
import java.net.Socket;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.PrivateKey;
import java.util.Random;

import security.SecureHelper;
import security.channel.AESChannel;
import security.channel.Channel;
import security.channel.RSAChannel;
import security.channel.TCPChannel;
import server.analytics.AnalyticsServer;
import server.billing.BillingServer;
import server.billing.BillingServerSecure;

import model.*;
import model.event.*;

import org.bouncycastle.util.encoders.Base64;

public class AuctionServerCommandHandlerThread extends Thread {
	Channel channel = null;
	private TCPChannel tcpChannel = null;
	private RSAChannel rsaChannel = null;
	private AESChannel aesChannel = null;
	private Key secretKey = null;
	private Socket socket = null;
	private User loggedInUser = null;
	private AuctionServerNotificationThread serverNotiThread = null;
	private int udpPort = 0;
	private Random random;
	private Registry registry;
	private AnalyticsServer analyticsServer;
	private BillingServer billingServer;
	private BillingServerSecure billingServerSecure;
	private String keyDirClient;

	public AuctionServerCommandHandlerThread(Socket socket,
			String analyticsName, String billingName, PrivateKey keyServer,
			String keyDirClient, String registryHost, int registryPort) {
		super("ServerCommandHandlerThread");
		this.keyDirClient = keyDirClient;

		try {
			this.socket = socket;
			this.tcpChannel = new TCPChannel(socket);
			this.rsaChannel = new RSAChannel(socket, "", keyServer);
			this.channel = rsaChannel;
		} catch (IOException e) {
			System.err.println("Error creating Channels in " + this.getClass()
					+ ": " + e.getMessage());
		}
		random = new Random();

		// get remote objects from registry
		try {
			registry = LocateRegistry.getRegistry(registryHost, registryPort);
			analyticsServer = (AnalyticsServer) registry.lookup(analyticsName);

		} catch (RemoteException e) {
			System.err.println("ERROR: Getting registry: " + e.getMessage());
			System.exit(1);
		} catch (NotBoundException e) {
			System.err.println("Caution: AnalyticServer-object not bound: "
					+ e.getMessage());
		}
		try {
			billingServer = (BillingServer) registry.lookup(billingName);
			this.billingServerSecure = billingServer.login("auctionClientUser",
					"auctionClientPass");
		} catch (RemoteException e) {
			System.err.println("REMOTE EXCEPTION: " + e.getMessage());
			System.exit(1);
		} catch (NotBoundException e) {
			System.err.println("Caution: BillingServer-object not bound: "
					+ e.getMessage());
			// System.exit(1);
		}
	}

	public void run() {
		try {
			String inputLine;
			while ((inputLine = channel.readLine()) != null) {

				if (inputLine.equals("!list")) {
					if (aesChannel == null) // if there's no secure channel
						channel = tcpChannel; // write with tcp channel

					String auctionList = ObjectManagementPool.getInstance()
							.listActualAuctions();
					if (this.loggedInUser != null) {
						if (this.secretKey == null) {
							this.secretKey = SecureHelper
									.createSharedSecretKey(keyDirClient
											+ this.loggedInUser.getUsername()
											+ ".key");
						}
						try {
							byte[] hMAC = SecureHelper.createHashMAC(secretKey,
									auctionList);
							byte[] base64hMAC = Base64.encode(hMAC);
							String base64HMACString = new String(base64hMAC);
							auctionList += "HMAC:" + base64HMACString;
						} catch (InvalidKeyException e) {
							System.err.println("ERROR: Secret shared key for "
									+ this.loggedInUser.getUsername()
									+ "not valid");
						}
					}
					channel.println(auctionList);

					if (aesChannel == null)
						channel = rsaChannel;
				}

				else if (inputLine.startsWith("!login")) {
					// make handshake with client and share aes-key
					aesChannel = SecureHelper.createAESChannelServer(
							keyDirClient, inputLine, socket);
					if (aesChannel == null) {
						// TODO �berlegen was passiert wenn fehler
						// war!!!!!!!!!!!
						channel = rsaChannel;
						continue;
					}
					channel = aesChannel; // if everything is ok, use aesChannel

					// User-object for this thread: its not the identic object
					// of the AuctionPool!
					User loginUser = new User(
							inputLine.split(" ")[1].toLowerCase(), true);
					if (ObjectManagementPool.getInstance().loginUser(
							new User(inputLine.split(" ")[1].toLowerCase(),
									true))) {
						if (this.loggedInUser == null) {
							this.loggedInUser = loginUser;
							channel.println("Successfully logged in as "
									+ this.loggedInUser.getUsername() + "!");

							// send event to analyticsServer
							if (analyticsServer != null) {
								try {
									Event loginEvent = new UserEvent(
											EventType.USER_LOGIN,
											this.loggedInUser.getUsername());
									this.analyticsServer
											.processEvent(loginEvent);
								} catch (RemoteException e) {
									System.out
											.println("Could not send login_event: AnalyticsServer shut down!");
								}
							}
						} else {
							channel.println("You are already logged in as "
									+ this.loggedInUser.getUsername() + "!");
							aesChannel = null;
							channel = rsaChannel;
						}
					} else { // if user is already logged on an other client,
						channel.println("User already in use with address: "
								+ channel.getInetAddress() + "!");
						aesChannel = null;
						channel = rsaChannel;
					}
				}

				else if (inputLine.equals("!logout")) {

					if (this.loggedInUser == null) {
						channel.println("You have to log in first!");
					} else {
						ObjectManagementPool
								.getInstance()
								.getCurrentUserByName(
										this.loggedInUser.getUsername())
								.logout();
						channel.println("Successfully logged out as "
								+ this.loggedInUser.getUsername() + "!");

						// send event to analyticsServer
						if (analyticsServer != null) {
							try {
								Event logoutEvent = new UserEvent(
										EventType.USER_LOGOUT,
										this.loggedInUser.getUsername());
								this.analyticsServer.processEvent(logoutEvent);
							} catch (RemoteException e) {
								System.out
										.println("Could not send logout_event: AnalyticsServer shut down!");
							}
						}

						this.loggedInUser = null;
						aesChannel = null;
						channel = rsaChannel;
						if (serverNotiThread != null) {
							serverNotiThread.interrupt();
						}
					}
				}

				else if (inputLine.equals("!end")) {
					if (this.loggedInUser != null) {
						ObjectManagementPool
								.getInstance()
								.getCurrentUserByName(
										this.loggedInUser.getUsername())
								.logout();
						// send event to analyticsServer
						if (analyticsServer != null) {
							try {
								Event disconnectEvent = new UserEvent(
										EventType.USER_DISCONNECTED,
										this.loggedInUser.getUsername());
								this.analyticsServer
										.processEvent(disconnectEvent);
							} catch (RemoteException e) {
								System.out
										.println("Could not send disconnected_event: AnalyticsServer shut down!");
							}
						}

						this.loggedInUser = null;
						this.channel.close();
						if (serverNotiThread != null) {
							serverNotiThread.interrupt();
						}
					}
					System.out.println("Client shut down.");
				}

				else if (inputLine.startsWith("!create")) {
					if (this.loggedInUser == null) {
						channel.println("You have to log in first!");
					} else {
						int auctionDurity = 0;
						try {
							auctionDurity = Integer.parseInt(inputLine
									.split(" ")[1]);
						} catch (NumberFormatException e) {
							System.out
									.println("Error parsing Number of Auction-Durity");
						}

						String[] splitted = inputLine.split(" "); // get
						// auction-description
						String auctionDescription = splitted[2];
						for (int i = 3; i < splitted.length; i++) {
							auctionDescription += " " + splitted[i];
						}

						Auction auction = new Auction(
								auctionDescription,
								auctionDurity,
								ObjectManagementPool
										.getInstance()
										.getCurrentUserByName(
												this.loggedInUser.getUsername()),
								auctionDurity, this.analyticsServer,
								this.billingServerSecure);
						if (ObjectManagementPool.getInstance().addAuction(
								auction)) {
							channel.println("An auction '" + auctionDescription
									+ "' with id " + auction.getId()
									+ " has been created and will end on "
									+ auction.getEndTime() + ".");

							// send event to analyticsServer
							if (analyticsServer != null) {
								try {
									Event createEvent = new AuctionEvent(
											EventType.AUCTION_STARTED,
											auction.getId(), auctionDurity);
									this.analyticsServer
											.processEvent(createEvent);
								} catch (RemoteException e) {
									System.out
											.println("Could not send auction_started_event: AnalyticsServer shut down!");
								}
							}
						}
					}
				}

				else if (inputLine.startsWith("!bid")) {
					if (this.loggedInUser == null) {
						channel.println("You have to log in first!");
					} else {
						long id = 0;
						double amount = 0;
						try {
							id = Long.parseLong(inputLine.split(" ")[1]);
							amount = Double
									.parseDouble(inputLine.split(" ")[2]);
						} catch (NumberFormatException e) {
							System.out
									.println("Error parsing Number of id or amount in !bid-message");
						}

						// loadTest bid request comes with id -1 and amount -1
						if (id == -1) {
							int listId = random
									.nextInt(ObjectManagementPool.getInstance()
											.getCurrentAuctions().size() + 1);
							Auction randomAuction = null;
							try {
								randomAuction = ObjectManagementPool
										.getInstance().getCurrentAuctions()
										.get(listId);
							} catch (ArrayIndexOutOfBoundsException e) {
							}
							if (randomAuction != null) {
								id = randomAuction.getId();
								amount = randomAuction.getRuntimeMillis();
							}
						}

						if (!ObjectManagementPool.getInstance()
								.isAuctionActual(id)) {
							channel.println("This Auction doesn't exist or is already expired!");
						} else {
							Auction bidAuction = ObjectManagementPool
									.getInstance().getCurrentAuctionById(id);
							User bidder = ObjectManagementPool.getInstance()
									.getCurrentUserByName(
											this.loggedInUser.getUsername());

							if (bidAuction.bid(amount, bidder)) {
								channel.println("You successfully bid with "
										+ amount + " on '"
										+ bidAuction.getDescription() + "'.");
							} else {
								channel.println("You unsuccessfully bid with "
										+ amount + " on '"
										+ bidAuction.getDescription()
										+ "'. Current highest bid is "
										+ bidAuction.getHighestBid() + ".");
							}
						}
					}
				} else if (inputLine.startsWith("!endLoadTest")) {
					for (User user : ObjectManagementPool.getInstance()
							.getCurrentUsers()) {
						if (user.getUsername().startsWith("loadTestingUser")) {
							System.out.println("user ausgelogged");
							user.logout();
						}
					}
					break;
				} else if (inputLine.startsWith("!groupBid")) {
					if (this.loggedInUser == null) {
						channel.println("You have to log in first!");
					} else {
						long id = 0;
						double amount = 0;
						try {
							id = Long.parseLong(inputLine.split(" ")[1]);
							amount = Double
									.parseDouble(inputLine.split(" ")[2]);
						} catch (NumberFormatException e) {
							System.out
									.println("Error parsing Number of id or amount in !bid-message");
						}

						if (ObjectManagementPool.getInstance().isAuctionActual(
								id)) {
							Auction groupBidAuction = ObjectManagementPool
									.getInstance().getCurrentAuctionById(id);
							User groupBidder = ObjectManagementPool
									.getInstance().getCurrentUserByName(
											this.loggedInUser.getUsername());
							String answer = ObjectManagementPool.getInstance()
									.addToGroupBidAuctions(groupBidAuction,
											groupBidder, amount, this.channel);
							if (answer.equals("placed")) {
								channel.println("You successfully started an groupbid for auction "
										+ groupBidAuction.getDescription()
										+ ".");
							} else {
								channel.println("Groupbid for auction "
										+ groupBidAuction.getDescription()
										+ " could not be started, " + answer);
							}
						} else {
							channel.println("An auction with id " + id
									+ "does not exist or is already expired.");
						}
					}
				} else if (inputLine.startsWith("!confirm")) {
					if (this.loggedInUser == null) {
						channel.println("You have to log in first!");
					} else {
						long id = 0;
						double amount = 0;
						String username = "";
						try {
							id = Long.parseLong(inputLine.split(" ")[1]);
							amount = Double
									.parseDouble(inputLine.split(" ")[2]);
						} catch (NumberFormatException e) {
							System.out
									.println("Error parsing Number of id or amount in !bid-message");
						}
						username = inputLine.split(" ")[3];
						ObjectManagementPool.getInstance()
								.confirmGroupBidAuction(this.loggedInUser,
										this.channel, id, amount, username);
					}
				} else { // must not be reached ever.
					channel.println("Error, falscher command!");
				}
			}
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (IOException e) {
			if (!this.channel.isClosed()) {
				System.err.println("Error reading from or writing to socket: "
						+ e.getMessage());
			}
			if (this.loggedInUser != null) {
				ObjectManagementPool.getInstance()
						.getCurrentUserByName(this.loggedInUser.getUsername())
						.logout();
			}
		}

		// free ressources
		try {
			channel.close();
			if (serverNotiThread != null) {
				serverNotiThread.interrupt();
			}
		} catch (IOException e1) {
			System.err.println("Error free ressourcing in " + this.getClass()
					+ ": " + e1.getMessage());
		}
	} // end of run()
} // end of class ServerCommandHandlerThread